﻿
using CloudStorageLight.Core.Web;
using Ionic.Zip;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.Queue;
using Microsoft.WindowsAzure.Storage.Table;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Runtime.Serialization;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Web;

namespace CloudStorageLight.Core
{
    /// <summary>
    /// Blobサービス
    /// </summary>
    public class BlobService :IDisposable
    {
        protected string ContainerName { get; set; }
        protected string ConnectionString { get; set; }

        public static readonly string LogDebug = "debug";
        public static readonly string LogUpload = "upload";
        public static readonly string LogDownload = "download";
        public static readonly string LogCrawl = "crawl";
        public static readonly string LogRecommended = "recommended";
        public static readonly string LogList = "list";
        public static readonly string LogGet = "get";
        public static readonly string LogSearch = "search";
        public static readonly string LogSearchById = "searchById";
        public static readonly string LogDelete = "delete";
        public static readonly string LogRecover = "recover";
        public static readonly string LogMove = "move";
        public static readonly string LogCreate = "create";
        public static readonly string LogUpdate = "update";
        public static readonly string LogCreateFolder = "createFolder";
        public static readonly string LogDeleteFolder = "deleteFolder";
        public static readonly string LogUpdateFullText = "updateFullText";
        public static readonly string LogRename = "rename";
        public static readonly string LogCheckOut = "checkOut";
        public static readonly string LogCheckIn = "checkIn";
        public static readonly string LogSet = "set";
        public static readonly string LogSaveTemp = "saveTemp";
        public static readonly string LogLoadTemp = "loadTemp";
        public static readonly string LogDeleteTemp = "deleteTemp";

        public CloudAccount CloudAccount
        {
            get { return _cloudAccount; }
        }
        private CloudAccount _cloudAccount;
        
        protected virtual BlobContainer Container 
        {
            get 
            {
                return _container;
            }
        }
        private BlobContainer _container;

        internal protected virtual FullTextServiceBase FullTextServiceInstance
        {
            get 
            {
                if (_fullTextService == null)
                {
                    _fullTextService = new FullTextServiceBase(fulltextsContainerName, ConnectionString);
                }
                return _fullTextService; 
            }
        }
        private FullTextServiceBase _fullTextService;


        private string fulltextsContainerName
        {
            get
            {
                if (_fulltextsContainerName == null) _fulltextsContainerName =ContainerName + "-catalog";
                return _fulltextsContainerName;
            }
        }
        private string _fulltextsContainerName;

        public string TempfulltextContainerName
        {
            get 
            {
                if (_tempfulltextContainerName == null) _tempfulltextContainerName = ContainerName + "-temp";
                return _tempfulltextContainerName; 
            }
        }
        private string _tempfulltextContainerName;

        public string UpdatefulltextQueueName
        {
            get 
            {
                if (_updatefulltextQueueName == null) _updatefulltextQueueName = ContainerName + "-fullqueue";
                return _updatefulltextQueueName; 
            }
        }
        private string _updatefulltextQueueName;

        public string CrawlQueueName
        {
            get
            {
                if (_crawlQueueName == null) _crawlQueueName = ContainerName + "-crawlqueue";
                return _crawlQueueName;
            }
        }
        private string _crawlQueueName;


        public LogService LogService
        {
            get 
            {
                if (_logService == null) _logService = new LogService(ConnectionString);
                return _logService; 
            }
        }
        private LogService _logService;

        private HttpApplicationState httpApplication;

        public BlobService()
            : this(GetCurrentCloudAccount(), SystemSettings.Instance.BlobStorage, SystemSettings.Instance.BlobContainerName)
        {

        }
        public BlobService(CloudAccount account, HttpApplicationState app = null)
            : this(account, SystemSettings.Instance.BlobStorage, SystemSettings.Instance.BlobContainerName)
        {
            httpApplication = app;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="account"></param>
        /// <param name="defaultConnectionString"></param>
        public BlobService(CloudAccount account, string defaultConnectionString, string defaultContainerName = "files")
        {
            this._cloudAccount = account;
            this.ContainerName = "c-" + (string.IsNullOrEmpty(account.AccountName) ? defaultContainerName : account.RowKey);
            this.ConnectionString = defaultConnectionString; 
            if (account == null) throw new ArgumentNullException("account");
            if (string.IsNullOrEmpty(account.Config))
            {
                //_container = new AzureBlobContainer(ContainerName, ConnectionString, "/");
                _container = new CompositeBlobContainer(ContainerName, ConnectionString, new BlobAdapterBase[]{});
            }
            else
            {
                var con = BlobContainerConfig.LoadFromString(account.Config);
                _container = con.CreateBlobContainer(ContainerName, defaultConnectionString);
            }
        }

        private async Task<string> CheckAndNormalizePathAsync(string path)
        {
            var caseInsentive = await IsCaseInsensitiveAsync(path);
            if (caseInsentive)
            {
                var parent =  BlobFile.GetDirectoryPath(path);
                var filename = BlobFile.GetFileName(path);
                var list = await Container.GetFiles(parent, 0);
                var item = list.Where(x => string.Equals(x.Name, filename, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
                if (item != null) return item.Path;
            }
            if (path.Length > 1024) throw new BlobException(BlobExceptionCode.TooLongFileName);
            if (path.EndsWith(".")) throw new BlobException(BlobExceptionCode.NotAcceptablePath);
            return path;
        }

        /// <summary>
        /// Blob上にフォルダを作成
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <param name="owner"></param>
        /// <returns></returns>
        public virtual async Task<BlobDirectory> CreateDirectoryAsync(BlobUser user, string path, string owner)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);
           
            if (!path.EndsWith("/")) throw new BlobException(BlobExceptionCode.InvalidDirectoryPath);
            var exists = await Container.ExistsAsync(path);
            if (exists) throw new BlobException(BlobExceptionCode.AlreadyExists);

            //if (path.Length > BlobUtil.MaxFolderLength) throw new BlobException(BlobExceptionCode.ExceedFolderUrlLength);
            path = await CheckAndNormalizePathAsync(path);

            var canAccess = await CanWriteAsync(user, GetParentPath(path));
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization); 

            var directory = await Container.EnsureDirectoryAsync(path, false);
            directory.Owner = owner;
            await directory.CreateAsync();
            if (string.IsNullOrEmpty(directory.Name)) throw new BlobException(BlobExceptionCode.InvalidDirectoryPath);
            await directory.SetMetadataAsync();

            EntryLog(user, LogCreateFolder, path, null);
            return directory;
        }


        /// <summary>
        /// ファイルのアップロード処理
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <param name="stream"></param>
        /// <param name="title"></param>
        /// <returns></returns>
        public virtual async Task<BlobFile> UploadFileAsync(BlobUser user, string path, Stream stream, string contentType = null, DateTimeOffset? lastWriteTime = null, DateTimeOffset? creationTime = null)
        {
            var info = "";
            path = await CheckAndNormalizePathAsync(path);

            if (! await ExistsAsync(BlobFile.GetDirectoryPath(path))) throw new BlobException(BlobExceptionCode.InvalidDirectoryPath); 

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanWriteAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            var file = await Container.EnsureFileAsync(path, true);
            if (file.IsCheckOut && !string.Equals(file.CheckOutUserId, user.Name, StringComparison.InvariantCultureIgnoreCase)) throw new BlobException(BlobExceptionCode.Locked);

            if (!await IsUploadableFileAsync(file)) throw new BlobException(BlobExceptionCode.RestrictFileType, file.Name);


            if (file.LastModified != null)
            {
                canAccess = await CanModifyAsync(user, path);
                if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);
                if (await IsBackupOnAsync(path))
                {
                    var cloudBlob = await Container.CreateArchiveAsync(file);
                    info = "snapshot:" + (cloudBlob.SnapshotTime.HasValue ? cloudBlob.SnapshotTime.ToString() : "");
                    await Container.PurgeBackupAsync(file, await GetMaxHistorySizeAsync(file.Path));
                }
            }

            file.Owner = user.Name;
            file.ContentType = contentType;
            file.UploadDate = DateTimeOffset.UtcNow;
            file.LastWriteTime = lastWriteTime;
            file.CreationTime = creationTime;
            await file.UploadAsync(stream);
            await file.SetMetadataAsync();

            EntryLog(user, LogUpload, path, info);
            return file;
        }

        /// <summary>
        /// ファイルのアップロード処理
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <param name="filename"></param>
        /// <param name="title"></param>
        /// <returns></returns>
        public virtual async Task<BlobFile> UploadFileAsync(BlobUser user, string path, string filename, string contentType = null, DateTimeOffset? lastWriteTime = null, DateTimeOffset? creationTime = null)
        {
            var info = "";
            path = await CheckAndNormalizePathAsync(path);

            if (!await ExistsAsync(BlobFile.GetDirectoryPath(path))) throw new BlobException(BlobExceptionCode.InvalidDirectoryPath); 

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanWriteAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            var file = await Container.EnsureFileAsync(path, true);
            if (file.IsCheckOut && !string.Equals(file.CheckOutUserId, user.Name, StringComparison.InvariantCultureIgnoreCase)) throw new BlobException(BlobExceptionCode.Locked);

            if (!await IsUploadableFileAsync(file)) throw new BlobException(BlobExceptionCode.RestrictFileType, file.Name);

            if (file.LastModified != null)
            {
                canAccess = await CanModifyAsync(user, path);
                if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);
                if (await IsBackupOnAsync(path))
                {
                    var cloudBlob = await Container.CreateArchiveAsync(file);
                    info = "snapshot:" + (cloudBlob.SnapshotTime.HasValue ? cloudBlob.SnapshotTime.ToString() : "");
                    await Container.PurgeBackupAsync(file, await GetMaxHistorySizeAsync(file.Path));
                }
            }

            file.Owner = user.Name;
            file.ContentType = contentType;
            file.UploadDate = DateTimeOffset.UtcNow;
            file.LastWriteTime = lastWriteTime ?? file.UploadDate;
            file.CreationTime = creationTime;
            await file.UploadAsync(filename);
            await file.SetMetadataAsync();
            Container.ClearCache(); 

            EntryLog(user, LogUpload, path, info);
            return file;
        }

        public virtual async Task<BlobFile> EnsureArchiveAsync(string path)
        {
            var file = await Container.EnsureFileAsync(path, true);
            return await EnsureAchchiveAsync(file);
        }

        public virtual async Task<BlobFile> EnsureAchchiveAsync(BlobFile file)
        {
            if (await IsBackupOnAsync(file.Path))
            {
                var blobConatin = await Container.CreateArchiveAsync(file);
                await Container.PurgeBackupAsync(file, await GetMaxHistorySizeAsync(file.Path));

                //file.LatestSnapshot = blobConatin.SnapshotTime;
                //file.ArchiveLastModified = file.LastModified;
                //await file.SetMetadataAsync();
                return file;
            }
            return null;
        }

        /// <summary>
        /// フルテキスト検索情報の登録
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <param name="title"></param>
        /// <param name="fulltext"></param>
        /// <returns></returns>
        public virtual async Task UpdateFullTextAsync(BlobUser user, string path, string title, string fulltext)
        {
            var sw = Stopwatch.StartNew();

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanWriteAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            if (FullTextServiceInstance != null) await FullTextServiceInstance.Entry(path, title, fulltext);

            var file = await Container.EnsureFileAsync(path, true);
            file.FullTextEntryDate = DateTime.UtcNow;
            await file.SetMetadataAsync();

            EntryLog(user, LogUpdateFullText, path, sw.Elapsed.ToString());
        }

        /// <summary>
        /// フルテキスト情報を取得
        /// </summary>
        /// <param name="user"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<string> GetFullTextAsync(BlobUser user, string path)
        {
            var sw = Stopwatch.StartNew();

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanReadAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            if (FullTextServiceInstance == null) return null;
            var text = FullTextServiceInstance.SearchById(path);

            EntryLog(user, LogSearchById, path, sw.Elapsed.ToString());
            if (text == null) return null;
            return text.ResultContent;
        }

        internal string GetFullText(string path)
        {
            if (FullTextServiceInstance == null) return null;
            var text = FullTextServiceInstance.SearchById(path);
            if (text == null) return null;
            return text.ResultContent;
        }

        /// <summary>
        /// ユーザを取得します。
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        public virtual BlobUser GetUser(IPrincipal principal)
        {
            return new BlobUser(principal);
        }

        /// <summary>
        /// パスの存在チェック
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<bool> ExistsAsync(string path)
        {
            return await Container.ExistsAsync(path);
        }

        /// <summary>
        /// 指定したディレクトリのファイル一覧を返します。
        /// ユーザの権限に応じてセキュリティトリミングします。
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<List<BlobItem>> ListFilesAsync(BlobUser user, string path, int maxSize=0, bool flat=false, bool deleted = false)
        {
            var sw = Stopwatch.StartNew();

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);
            var retList = new List<BlobItem>();

            var accessTuple = await CanAccessDirectoryAsync(path, user, "r");
            if (deleted)
            {
                var accessControlTuple = await CanAccessDirectoryAsync(path, user, "c");
                accessTuple = new Tuple<bool, bool>(accessTuple.Item1 & accessControlTuple.Item1, accessTuple.Item2 & accessControlTuple.Item2);
            }
            var canAccessDire = accessTuple.Item1; // await CanReadAsync(user, path);
            var ownerAccess = accessTuple.Item2;

            //個別セキュリティのみに基づくように変更
            //if (!canAccessDire) return retList;
            var dire = await Container.EnsureDirectoryAsync(path);
            IEnumerable<BlobItem> list = await Container.GetFiles(path, maxSize, flat, deleted);
            if (maxSize > 0) list = list.Take(maxSize);
            //セキュリティトリミング
            foreach(var item in list)
            {
                var ret = CanAccess(item, user, "r", ownerAccess);
                if (ret.HasValue ? ret.Value : canAccessDire) retList.Add(item);
            }
            EntryLog(user, LogList, path, sw.Elapsed.ToString());

            return retList;
        }


        /// <summary>
        /// ファイルまたはディレクトリを取得します
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<BlobItem> GetBlobItemAsync(BlobUser user, string path)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanReadAsync(user, path);
            if (path.EndsWith("/"))
            {
                var dire = await Container.EnsureDirectoryAsync(path, true);
                EntryLog(user, LogGet, path, null);
                return dire;
            }
            else
            {
                var file = await Container.EnsureFileAsync(path, true);
                EntryLog(user, LogGet, path, null);
                return file;
            }
        }


        /// <summary>
        /// 指定した条件で全文検索します。
        /// 
        /// 指定したパス以下のフォルダが対象です。
        /// ユーザの権限でセキュリティトリミングされます。
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="query"></param>
        /// <param name="path"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        public virtual async Task<List<BlobItem>> SearchAsync(BlobUser user, string query, string path, int size, int resultContentsCount = 3, int resultContentSize = 200)
        {
            var sw = Stopwatch.StartNew();

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);
            if (string.IsNullOrWhiteSpace(query)) throw new BlobException(BlobExceptionCode.InvalidQuery);

            var retList = new List<BlobItem>();

            var keywords = query.Split(' ').Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x))
                    .OrderByDescending(x => x.Length)
                    .Select(x => FullTextServiceInstance.EscapeQuery(x))
                    .ToList();
            var targetQuery = string.Format("(Body:({1}) OR Title:({1}))", path, string.Join(" AND ", keywords));

            if (keywords.Any(x => x.ToUpper() == "AND" || x.ToUpper() == "OR"))
            {
                targetQuery = string.Format("(Body:({1}) OR Title:({1}))", path, query);
            }

            keywords = keywords.Where(x => x.ToUpper() != "AND" && x.ToUpper() != "OR").ToList();

            if (FullTextServiceInstance == null) throw new InvalidOperationException();

            var result = FullTextServiceInstance.SearchByRawQuery(targetQuery, int.MaxValue);
            foreach (var resultItem in result)
            {
                //if (retList.Any(x => x.Path == resultItem.ResultId)) continue;
                if (!resultItem.ResultId.StartsWith(path, StringComparison.CurrentCultureIgnoreCase)) continue;
                var ret = await ExistsAsync(resultItem.ResultId) && await CanReadAsync(user, resultItem.ResultId);
                if (ret)
                {
                    var file = await Container.EnsureFileAsync(resultItem.ResultId, true);
                    var startIndex = 0;
                    var totalLen = resultItem.ResultContent.Length;
                    var count = 0;
                    file.SearchContent = "";
                    while (true)
                    {

                        var pos = -1;
                        foreach (var keyword in keywords)
                        {
                            var tempPos = resultItem.ResultContent.IndexOf(keyword, startIndex, StringComparison.CurrentCultureIgnoreCase);
                            if (tempPos != -1)
                            {
                                if (pos == -1)
                                {
                                    pos = tempPos;
                                }
                                else if (pos > tempPos)
                                {
                                    pos = tempPos;
                                }
                            }
                        }
                        if (pos == -1) break;
                        var nextpos = Math.Min(pos + resultContentSize, totalLen);
                        var desc = resultItem.ResultContent.Substring(pos, nextpos - pos);

                        if (nextpos - pos == resultContentSize) desc += "...";
                        file.SearchContent += desc;

                        if (nextpos >= totalLen) break;
                        count++;
                        if (count >= 3) break;
                        startIndex = nextpos;
                    }
                    retList.Add(file);
                    if (retList.Count >= size) break;
                }
            }

            EntryLog(user, LogSearch, path, query + " / " + sw.Elapsed.ToString());
            return retList;
        }

        /// <summary>
        /// ファイルをダウンロードします。
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <param name="stream"></param>
        /// <param name="checkout"></param>
        /// <returns></returns>
        public virtual async Task DownloadFileAsync(BlobUser user, string path, Stream stream)
        {
            var sw = Stopwatch.StartNew();

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanReadAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            var file = await Container.EnsureFileAsync(path);
            await Container.DownloadAsync(file, stream);
            EntryLog(user, LogDownload, file.Path, sw.Elapsed.ToString());

        }

        /// <summary>
        /// 古いバージョンのファイルをダウンロードします。
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <param name="stream"></param>
        /// <param name="checkout"></param>
        /// <returns></returns>
        public virtual async Task DownloadArchiveAsync(BlobUser user, string path, DateTimeOffset snapshot, Stream stream)
        {
            var sw = Stopwatch.StartNew();

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanReadAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            var file = await Container.EnsureFileAsync(path);
            file.Snapshot = snapshot;
            await Container.DownloadArchiveAsync(file, stream);

            EntryLog(user, LogDownload, file.Path, sw.Elapsed.ToString());

        }


        /// <summary>
        /// ファイルを削除します
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task DeleteFileAsync(BlobUser user, string path, bool completely = false)
        {
            if (path.EndsWith("/")) throw new ArgumentException("path");

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanWriteAsync(user, path) && await CanModifyAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);


            var file = await Container.EnsureFileAsync(path, true);
            if (file.IsCheckOut && !string.Equals(file.CheckOutUserId, user.Name, StringComparison.InvariantCultureIgnoreCase)) throw new BlobException(BlobExceptionCode.Locked);
            var backupMode = await IsBackupOnAsync(path) && !completely;
            if (backupMode)
            {
                await Container.CreateArchiveAsync(file);
                await Container.PurgeBackupAsync(file, await GetMaxHistorySizeAsync(file.Path));
            }
            if (await Container.DeleteAsync(file)) EntryLog(user, LogDelete, file.Path, null);
            if (!backupMode) await Container.DeleteBlobAsync(file);
        }

        /// <summary>
        /// ファイルを回復します
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task RecoverFileAsync(BlobUser user, string path)
        {
            if (path.EndsWith("/")) throw new ArgumentException("path");

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanWriteAsync(user, path) && await CanModifyAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);


            var file = await Container.EnsureFileAsync(path, true);
            if (await Container.RecoverAsync(file)) EntryLog(user, LogRecover, file.Path, null);
        }

        /// <summary>
        /// ファイルを移動します
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task MoveOrCopyFileAsync(BlobUser user, string path, string targetPath, bool deleteProc=true)
        {
            if (path.EndsWith("/")) throw new ArgumentException("path");

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanReadAsync(user, path);
            var protectedMode = await GetProtectedModeAsync(path);
            if (deleteProc) canAccess = canAccess && await CanWriteAsync(user, path) && await CanModifyAsync(user, path);
            if (protectedMode != "None") canAccess = canAccess && await CanControlAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);
            var sourceFile = await Container.EnsureFileAsync(path);
            if (deleteProc)
            {
                //チエックアウトしているファイルの移動はできない
                if (sourceFile.IsCheckOut) throw new BlobException(BlobExceptionCode.CheckoutLocked, sourceFile.Name);
            }

            if (targetPath.EndsWith("/"))
            {
                targetPath = targetPath + sourceFile.Name;
            }
            targetPath = await CheckAndNormalizePathAsync(targetPath);
            if (path == targetPath) throw new ArgumentException("targetPath");

            var targetFile = await Container.EnsureFileAsync(targetPath, true);
            if (targetFile.IsCheckOut && !string.Equals(targetFile.CheckOutUserId, user.Name, StringComparison.InvariantCultureIgnoreCase)) throw new BlobException(BlobExceptionCode.Locked);
            if (targetFile.LastModified != null)
            {
                canAccess = await CanModifyAsync(user, targetPath);
                if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);
                if (await IsBackupOnAsync(targetPath))
                {
                    await Container.CreateArchiveAsync(targetFile);
                    await Container.PurgeBackupAsync(targetFile, await GetMaxHistorySizeAsync(targetFile.Path));
                }
            }
            else
            {
                //仕様変更：保存先を物理削除→履歴復活
                //await Container.DeleteBlobAsync(targetFile);
            }

            await Container.CopyAsync(sourceFile, targetPath);
            //targetFile = await Container.EnsureFileAsync(targetPath, true);
            foreach(var item in targetFile.Metadata.ToArray())
            {
                targetFile.Metadata[item.Key] = null;
            }
            targetFile.Owner = sourceFile.Owner;
            targetFile.LastWriteTime = sourceFile.LastWriteTime;
            targetFile.CreationTime = sourceFile.CreationTime;
            await targetFile.SetMetadataAsync();

            if (deleteProc)
            {
                await Container.DeleteAsync(sourceFile);
                await Container.DeleteBlobAsync(sourceFile);
            }
            EntryLog(user, LogMove, targetFile.Path, null);
        }



        /// <summary>
        /// ファイル名の変更
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="srcPath"></param>
        /// <param name="distPath"></param>
        /// <returns></returns>

        public virtual async Task RenameFileAsync(BlobUser user, string srcPath, string distPath)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var srcFile = await Container.EnsureFileAsync(srcPath, true);
            if (distPath.EndsWith("/"))
            {
                distPath = distPath + srcFile.Name;
            }
            if (srcPath == distPath) throw new BlobException(BlobExceptionCode.CannotRenameSameFileName);

            var distPathN = await CheckAndNormalizePathAsync(distPath);

            if (srcPath != distPathN) distPath = distPathN;

            var canSrcAccess = await CanWriteAsync(user, srcPath) && await CanModifyAsync(user, srcPath);
            var protectedMode = await GetProtectedModeAsync(srcPath);
            if (protectedMode != "None") canSrcAccess = canSrcAccess && await CanControlAsync(user, srcPath);
            if (!canSrcAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            var canDistAccess = await CanWriteAsync(user, distPath);
            if (!canDistAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            var distFile = await Container.EnsureFileAsync(distPath, true);
            if (distFile.LastModified != null)
            {
                throw new BlobException(BlobExceptionCode.AlreadyExists);
                //canDistAccess = await CanWriteAsync(user, distPath);
                //if (!canDistAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);
            }
            //仕様変更：チェックアウトしている場合はたとえ自分でもエラーにする（編集状態を保証する）
            if (srcFile.IsCheckOut) throw new BlobException(BlobExceptionCode.CheckoutLocked, srcFile.Name);

            if (distFile.IsCheckOut && !string.Equals(distFile.CheckOutUserId, user.Name, StringComparison.InvariantCultureIgnoreCase)) throw new BlobException(BlobExceptionCode.Locked);

            //仕様変更：保存先を物理削除→履歴復活
            //await Container.DeleteBlobAsync(distFile);
            //履歴をコピー
            await Container.CopyWithSnapshotAsync(srcFile, distFile);
            //実体をコピー
            await Container.CopyAsync(srcFile, distFile.Path);
            foreach (var item in distFile.Metadata.ToArray())
            {
                distFile.Metadata[item.Key] = null;
            }
            distFile.Owner = srcFile.Owner;
            distFile.CheckOutUserId = srcFile.CheckOutUserId;
            distFile.LastWriteTime = srcFile.LastWriteTime;
            distFile.CreationTime = srcFile.CreationTime;
            await distFile.SetMetadataAsync();


            //元を完全削除
            await Container.DeleteAsync(srcFile);
            await Container.DeleteBlobAsync(srcFile);

            EntryLog(user, LogRename, distFile.Path, srcPath);
        }


        /// <summary>
        /// フォルダを削除します
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task DeleteDirectoryAsync(BlobUser user, string path)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanModifyAsync(user, path);
            var canControl = await CanControlAsync(user, path);
            if (!canAccess || !canControl) throw new BlobException(BlobExceptionCode.NotAuthorization);
            if (path == "/") throw new BlobException(BlobExceptionCode.NotAuthorization);

            var file = await Container.EnsureDirectoryAsync(path, true);
            var children = await file.GetChildren(2);
            if (children.Count != 0 && (children.Count != 1 || children[0].Path != path)) throw new BlobException(BlobExceptionCode.InvalidOperationRemoveNotEmptyFolder);
            if (await Container.DeleteAsync(file)) EntryLog(user, LogDeleteFolder, file.Path, null);
            await Container.DeleteBlobAsync(file);
        }

        /// <summary>
        /// ファイルがチェックアウトされているか
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<bool> IsCheckOutOtherAsync(BlobUser user, string path)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var exists = await Container.ExistsAsync(path);
            if (!exists) throw new BlobException(BlobExceptionCode.NotFoundFile);

            var file = await Container.EnsureFileAsync(path, true);
            if (file.IsCheckOut && !string.Equals(file.CheckOutUserId, user.Name, StringComparison.InvariantCultureIgnoreCase)) return true;
            return false;
        }

        /// <summary>
        /// ファイルをチェックアウトします
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<BlobItem> CheckOutAsync(BlobUser user, string path, string lockOwner = null, int locktimeout = 0, string opaqueLock = null)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var exists = await Container.ExistsAsync(path);
            if (!exists) throw new BlobException(BlobExceptionCode.NotFoundFile);

            var canAccess = await CanModifyAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);
            var file = await Container.EnsureFileAsync(path, true);
            if (file.IsCheckOut && !string.Equals(file.CheckOutUserId, user.Name, StringComparison.InvariantCultureIgnoreCase)) throw new BlobException(file.CheckOutUserId == user.Name ? BlobExceptionCode.AlreadyCheckouted : BlobExceptionCode.Locked);
            if (!await Container.CheckOutActionAsync(user, file))
            {
                throw new BlobException(BlobExceptionCode.Locked);
            }

            file.CheckOutUserId = user.Name;
            if (opaqueLock == null) opaqueLock = Guid.NewGuid().ToString("D");
            file.OpaqueLock = opaqueLock;
            if (locktimeout > 0) file.LockTimeout = DateTimeOffset.UtcNow.AddSeconds(locktimeout);
            file.LockOwner = lockOwner ?? user.Name;
            await file.SetMetadataAsync();

            EntryLog(user, LogCheckOut, file.Path, null);

            return file;
        }

        /// <summary>
        /// ファイルをチェックインします。
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<BlobItem> CheckInAsync(BlobUser user, string path)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var exists = await Container.ExistsAsync(path);
            if (!exists) throw new BlobException(BlobExceptionCode.NotFoundFile);

            var file = await Container.EnsureFileAsync(path, true);
            if (!file.IsCheckOut) throw new BlobException(BlobExceptionCode.NotCheckOut);
            var canAccess = await CanControlAsync(user, path);
            if (!canAccess && !string.Equals(file.CheckOutUserId, user.Name, StringComparison.InvariantCultureIgnoreCase)) throw new BlobException(BlobExceptionCode.Locked);

            if (!await Container.CheckInActionAsync(user, file))
            {
                throw new BlobException(BlobExceptionCode.Locked);
            }


            file.CheckOutUserId = null;
            file.OpaqueLock = null;

            EntryLog(user, LogCheckIn, file.Path, null);
            await file.SetMetadataAsync();
            return file;
        }

 
        /// <summary>
        /// 読み込み可能か
        /// </summary>
        /// <param name="user"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<bool> CanReadAsync(BlobUser user, string path)
        {
            return await CanAccessAsync(path, user, "r");
        }

        /// <summary>
        /// 上書き可能か
        /// </summary>
        /// <param name="user"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<bool> CanModifyAsync(BlobUser user, string path)
        {
            return await CanAccessAsync(path, user, "m");
        }

        /// <summary>
        /// 書き込み可能か
        /// </summary>
        /// <param name="user"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<bool> CanWriteAsync(BlobUser user, string path)
        {
            return await CanAccessAsync(path, user, "w");
        }

        /// <summary>
        /// コントロール可能か
        /// </summary>
        /// <param name="user"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<bool> CanControlAsync(BlobUser user, string path)
        {
            return await CanAccessAsync(path, user, "c");
        }

        private async Task<bool> CanAccessAsync(string path, BlobUser user, string mode)
        {
            if (user.IsAdmin) return true;
            var list = await LoadPathTree(path);

            return CanAccess(user, list, mode);
        }

        private bool CanAccess(BlobUser user, List<BlobItem> list, string mode)
        {
            int idx = 0;
            //CHECK USER
            foreach (var item in list)
            {
                var ownerAccess = true; //Ownerの場合アクセスを許可する
                //OWNER CHECK
                if (string.Equals(item.Owner, user.Name, StringComparison.InvariantCultureIgnoreCase))
                {
                    for (int i = idx; i < list.Count; i++)
                    {
                        var owner = list[i].AccessTokens.Where(x => string.Equals(x.Role, "owner", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
                        if (owner != null)
                        {
                            //Ownerが明示的に指定され、ONの場合アクセスOK
                            if (owner.CanAccess(mode)) return true;

                            //OFFの場合はOwnerであってもアクセス許可しない
                            ownerAccess = false;
                            break;
                        }

                    }
                    if (ownerAccess) return true;
                }

                var access = CanAccess(item, user, mode, false);
                if (access.HasValue) return access.Value;

                idx++;
            }

            return user.IsAdmin;
        }

        private async Task<Tuple<bool, bool>> CanAccessDirectoryAsync(string path, BlobUser user, string mode)
        {
            if (user.IsAdmin) return  new Tuple<bool,bool>(true,true);
            var list = await LoadPathTree(path);

            //OWNER CHECK
            var ownerAccess = true;
            foreach (var item in list)
            {
                var owner = item.AccessTokens.Where(x => string.Equals(x.Role, "owner", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
                if (owner != null)
                {
                    ownerAccess = owner.CanAccess(mode);
                    break;
                }
            }
            return new Tuple<bool, bool>(CanAccess(user, list, mode), ownerAccess); ;
        }

        private async Task<List<BlobItem>> LoadPathTree(string path)
        {
            var list = new List<BlobItem>();
            while (path != null)
            {
                var exists = await Container.ExistsAsync(path);
                if (exists)
                {
                    BlobItem item;
                    if (path.EndsWith("/"))
                    {
                        item = await Container.EnsureDirectoryAsync(path, true);
                    }
                    else
                    {
                        item = await Container.EnsureFileAsync(path, true);
                    }
                    list.Add(item);
                }
                path = GetParentPath(path);
            }
            return list;
        }

        private bool? CanAccess(BlobItem item, BlobUser user, string mode, bool owner)
        {
            if (user.IsAdmin) return true;
            if (owner)
            {
                //Ownerルールを提供する場合、onwerアクセス権を調べ存在しない場合はアクセス許可
                if (string.Equals(item.Owner,user.Name, StringComparison.InvariantCultureIgnoreCase))
                {
                    var ownerAccess = item.AccessTokens.Where(x => string.Equals(x.Role, "owner", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
                    if (ownerAccess != null) return ownerAccess.CanAccess(mode);
                    return true;
                }
            }
            var tokens = item.AccessTokens.Where(x => user.IsInRole(x.Role));
            foreach (var token in tokens)
            {
                if (token.CanAccess(mode)) return true;
            }
            if (item.UsersRoleMembers.Any(x => string.Equals(x, user.Name, StringComparison.InvariantCultureIgnoreCase)))
            {
                var users = item.AccessTokens.Where(x => x.Role == "users").FirstOrDefault();
                if (users != null)
                {
                    if (users.CanAccess(mode)) return true;
                }

            }
            if (user.IsGuest)
            {
                if (!CloudAccount.CanUseGuestAccount) return false;
                var guest = item.AccessTokens.Where(x => x.Role == "guest").FirstOrDefault();
                if (guest == null)
                {
                    if (item is BlobDirectory) return false;
                }
                else
                {
                    return guest.CanAccess(mode);
                }
            }
            var everyoneToken = item.AccessTokens.Where(x => x.Role == "everyone").FirstOrDefault();
            if (everyoneToken != null) return everyoneToken.CanAccess(mode);
            return null;
        }

        /// <summary>
        /// バックアップ対象のファイルか
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<bool> IsBackupOnAsync(string path)
        {
            var exists = await Container.ExistsAsync(path);
            if (exists)
            {
                BlobItem item;
                if (path.EndsWith("/"))
                {
                    item = await Container.EnsureDirectoryAsync(path, true);
                }
                else
                {
                    item = await Container.EnsureFileAsync(path, true);
                }
                
                if (item.BackupMode.HasValue)
                {
                    return item.BackupMode.Value;
                }
            }

            var parentPath = GetParentPath(path);
            if (parentPath == null) return true;
            return await IsBackupOnAsync(parentPath);
        }

        /// <summary>
        /// 検索対象のファイルか
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<bool> IsSerachedOnAsync(string path)
        {
            var exists = await Container.ExistsAsync(path);
            if (exists)
            {
                BlobItem item;
                if (path.EndsWith("/"))
                {
                    item = await Container.EnsureDirectoryAsync(path, true);
                }
                else
                {
                    item = await Container.EnsureFileAsync(path, true);
                }

                if (item.SearchedMode.HasValue)
                {
                    return item.SearchedMode.Value;
                }
            }

            var parentPath = GetParentPath(path);
            if (parentPath == null) return true;
            return await IsSerachedOnAsync(parentPath);
        }

        /// <summary>
        /// 大文字小文字を区別するか
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<bool> IsCaseInsensitiveAsync(string path)
        {
            var exists = await Container.ExistsAsync(path);
            if (exists)
            {
                BlobItem item;
                if (path.EndsWith("/"))
                {
                    item = await Container.EnsureDirectoryAsync(path, true);
                }
                else
                {
                    item = await Container.EnsureFileAsync(path, true);
                }

                if (item.CaseInsensitive.HasValue)
                {
                    return item.CaseInsensitive.Value;
                }
            }

            var parentPath = GetParentPath(path);
            if (parentPath == null) return false;
            return await IsCaseInsensitiveAsync(parentPath);
        }

        /// <summary>
        /// パスワードで解除可能なファイルのみアップロード可能
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<bool> IsDecryptableFileOnlyAsync(string path)
        {
            var exists = await Container.ExistsAsync(path);
            if (exists)
            {
                BlobItem item;
                if (path.EndsWith("/"))
                {
                    item = await Container.EnsureDirectoryAsync(path, true);
                }
                else
                {
                    item = await Container.EnsureFileAsync(path, true);
                }

                if (item.DecryptableFileOnly.HasValue)
                {
                    return item.DecryptableFileOnly.Value;
                }
            }

            var parentPath = GetParentPath(path);
            if (parentPath == null) return false;
            return await IsDecryptableFileOnlyAsync(parentPath);
        }


        public virtual async Task<bool> IsDownloadFromOriginalUrlAsync(string path)
        {
            var exists = await Container.ExistsAsync(path);
            if (exists)
            {
                BlobItem item;
                if (path.EndsWith("/"))
                {
                    item = await Container.EnsureDirectoryAsync(path, true);
                }
                else
                {
                    item = await Container.EnsureFileAsync(path, true);
                }

                if (item.DownloadFromOriginalUrl.HasValue)
                {
                    return item.DownloadFromOriginalUrl.Value;
                }
            }

            var parentPath = GetParentPath(path);
            if (parentPath == null) return false;
            return await IsDownloadFromOriginalUrlAsync(parentPath);
        }

        public virtual async Task<bool> IsUploadableFileAsync(BlobFile file)
        {
            var exts = await GetUploadableFileExtensionAsync(file.Path);
            if (exts == null) return true;
            var ext = Path.GetExtension(file.Name);
            return exts.Split(',').Select(x=> "." +x.TrimStart('.'))
                .Any(x => string.Equals(x, ext, StringComparison.InvariantCultureIgnoreCase));
        }

        /// <summary>
        /// アップロード可能な拡張子
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<string> GetUploadableFileExtensionAsync(string path)
        {
            var exists = await Container.ExistsAsync(path);
            if (exists)
            {
                BlobItem item;
                if (path.EndsWith("/"))
                {
                    item = await Container.EnsureDirectoryAsync(path, true);
                }
                else
                {
                    item = await Container.EnsureFileAsync(path, true);
                }

                if (item.UploadableFileExtension != null)
                {
                    return item.UploadableFileExtension;
                }
            }

            var parentPath = GetParentPath(path);
            if (parentPath == null) return null;
            return await GetUploadableFileExtensionAsync(parentPath);
        }


        /// <summary>
        /// ファイルパスコードを取得
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<string> GetFilePasscodeAsync(string path)
        {
            var exists = await Container.ExistsAsync(path);
            if (exists)
            {
                BlobItem item;
                if (path.EndsWith("/"))
                {
                    item = await Container.EnsureDirectoryAsync(path, true);
                }
                else
                {
                    item = await Container.EnsureFileAsync(path, true);
                }

                if (item.FilePasscode != null)
                {
                    return item.FilePasscode;
                }
            }

            var parentPath = GetParentPath(path);
            if (parentPath == null) return null;
            return await GetFilePasscodeAsync(parentPath);
        }

        /// <summary>
        /// RMSテンプレートIDを取得
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<string> GetRmsTemplateIdAsync(string path)
        {
            var exists = await Container.ExistsAsync(path);
            if (exists)
            {
                BlobItem item;
                if (path.EndsWith("/"))
                {
                    item = await Container.EnsureDirectoryAsync(path, true);
                }
                else
                {
                    item = await Container.EnsureFileAsync(path, true);
                }

                if (item.RmsTemplateId != null)
                {
                    return item.RmsTemplateId;
                }
            }

            var parentPath = GetParentPath(path);
            if (parentPath == null) return null;
            return await GetRmsTemplateIdAsync(parentPath);
        }

        /// <summary>
        /// 保護モードを取得
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<string> GetProtectedModeAsync(string path)
        {
            var exists = await Container.ExistsAsync(path);
            if (exists)
            {
                BlobItem item;
                if (path.EndsWith("/"))
                {
                    item = await Container.EnsureDirectoryAsync(path, true);
                }
                else
                {
                    item = await Container.EnsureFileAsync(path, true);
                }

                if (item.ProtectedMode != null)
                {
                    return item.ProtectedMode;
                }
                if (item.FilePasscode != null)
                {
                    return "Password";
                }
                if (item.RmsTemplateId != null)
                {
                    return "RMS";
                }
            }

            var parentPath = GetParentPath(path);
            if (parentPath == null) return "None";
            return await GetProtectedModeAsync(parentPath);
        }


        /// <summary>
        /// 履歴最大数を取得
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<int> GetMaxHistorySizeAsync(string path)
        {
            var exists = await Container.ExistsAsync(path);
            if (exists)
            {
                BlobItem item;
                if (path.EndsWith("/"))
                {
                    item = await Container.EnsureDirectoryAsync(path, true);
                }
                else
                {
                    item = await Container.EnsureFileAsync(path, true);
                }

                if (item.MaxHistorySize != null)
                {
                    return item.MaxHistorySize.Value;
                }
            }

            var parentPath = GetParentPath(path);
            if (parentPath == null) return 20;
            return await GetMaxHistorySizeAsync(parentPath);
        }
        private string GetParentPath(string path)
        {
            if (path == "/") return null;
            if (path.EndsWith("/")) path = path.Substring(0, path.Length - 1);
            var items = path.Split('/');
            return path.Substring(0, path.Length - items[items.Length - 1].Length);
        }

        /// <summary>
        /// ログの書き込み
        /// </summary>
        /// <param name="user"></param>
        /// <param name="action"></param>
        /// <param name="path"></param>
        /// <param name="Additional"></param>
        /// <returns></returns>
        public virtual void EntryLog(BlobUser user, string action, string path, string Additional, HttpApplicationState app = null)
        {
            //パフォーマンスアップ 優先度の低いアクセス操作はログしない
            if (action == LogGet || action == LogList) return;

            var name = (string.IsNullOrEmpty(user.OperatorName)|| user.OperatorName == user.Name) ? user.Name : user.OperatorName +"(" + user.Name +")";
            LogService.EntryLog(name, CloudAccount.AccountName, action, path, Additional, app ?? httpApplication);
        }

        private static object lockObject = new object();

        public virtual async Task<List<AccessLog>> GetLog(DateTime? from, DateTime? to, string userName, string[] targetAction, string path = null, int maxRecords = 0)
        {
            return await LogService.GetLog(CloudAccount.AccountName, from, to, userName, targetAction, path, maxRecords);
        }
        public virtual List<FileAccessLog> GetFileLog(string path, int maxRecords = 0)
        {
            return LogService.GetFileLog(CloudAccount.AccountName, path, maxRecords);
        }

        public virtual async Task EnqueuDeleteFullTextAsync(BlobUser user, string path)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanWriteAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);
            var queue = GetFulltextQueueClient();

            var data = "delete\n" + path;
            CloudQueueMessage message = new CloudQueueMessage(data);
            queue.AddMessage(message);
        }

        public virtual async Task EnqueuUpdateFullTextAsync(BlobUser user, string path, string title, string fulltext)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanWriteAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            await EnqueueInternal(path, title, fulltext, user.Name);
        }

        private async Task EnqueueInternal(string path, string title, string fulltext, string name)
        {
            var queue = GetFulltextQueueClient();
            string data;
            if (fulltext == null)
            {
                data = "add\n" + path + "\n" + title + "\n" + name + "\n";
            }
            else
            {
                data = "update\n" + path + "\n" + title + "\n" + name + "\n" + fulltext;
                if (System.Text.Encoding.Unicode.GetByteCount(data) > 32000)
                {
                    var id = Guid.NewGuid().ToString();
                    var work = GetTempFullText();
                    var blob = work.GetBlockBlobReference(id);
                    await blob.UploadTextAsync(data);
                    data = "update\n" + id;
                }

            }
            CloudQueueMessage message = new CloudQueueMessage(data);
            queue.AddMessage(message);
        }

        public virtual async Task EnqueuCopyFullTextAsync(BlobUser user, string srcPath, string distPath)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanWriteAsync(user, distPath);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);


            var queue = GetFulltextQueueClient();

            var data = "copy\n" + srcPath + "\n" + distPath + "\n" + user.Name;
            CloudQueueMessage message = new CloudQueueMessage(data);
            queue.AddMessage(message);
        }

        public virtual async Task EnqueuRenameFullTextAsync(BlobUser user, string srcPath, string distPath)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanWriteAsync(user, distPath);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);


            var queue = GetFulltextQueueClient();

            var data = "rename\n" + srcPath + "\n" + distPath + "\n" + user.Name;
            CloudQueueMessage message = new CloudQueueMessage(data);
            queue.AddMessage(message);
        }

        private CloudBlobContainer GetTempFullText()
        {
            var storageAccount = CloudStorageAccount.Parse(ConnectionString);
            var blobClient = storageAccount.CreateCloudBlobClient();
            var workCont = blobClient.GetContainerReference(TempfulltextContainerName);
            workCont.CreateIfNotExists();
            return workCont;
        }

        private CloudQueue GetFulltextQueueClient()
        {
            var storageAccount = CloudStorageAccount.Parse(ConnectionString);
            var queueClient = storageAccount.CreateCloudQueueClient();
            var queue = queueClient.GetQueueReference(UpdatefulltextQueueName);
            queue.CreateIfNotExists();
            return queue;
        }

        private CloudQueue GetCrawlQueueClient()
        {
            var storageAccount = CloudStorageAccount.Parse(ConnectionString);
            var queueClient = storageAccount.CreateCloudQueueClient();
            var queue = queueClient.GetQueueReference(CrawlQueueName);
            queue.CreateIfNotExists();
            return queue;
        }

        public virtual async Task<bool> ExecuteUpdateFullTextSub()
        {
            var queue = GetFulltextQueueClient();
            CloudQueueMessage retrievedMessage = queue.GetMessage(new TimeSpan(0, 10, 0));
            if (retrievedMessage == null) return false; 
            try
            {
                var items = retrievedMessage.AsString.Split('\n');
                if (items[0] == "delete")
                {
                    await FullTextServiceInstance.DeleteEntry(items[1]);

                }
                else if (items[0] == "add")
                {
                    var file = await Container.EnsureFileAsync(items[1], true);
                    var fulltext = await GetFullTextContent(file, false);
                    await FullTextServiceInstance.Entry(items[1], items[2], fulltext, items[3]);

                    //セキュリティ上テキスト出力は行わない
                    EntryLog(new BlobUser(items[3]), "FullTextUpdate", items[1], "");
                }
                else if (items[0] == "update")
                {
                    string guid = null;
                    CloudBlockBlob blob = null;
                    Exception workex = null;
                    try
                    {
                        if (items.Length < 5)
                        {
                            var work = GetTempFullText();
                            blob = work.GetBlockBlobReference(items[1]);
                            guid = items[1];
                            if (!await blob.ExistsAsync()) throw new InvalidDataException("not found work");
                            var content = await blob.DownloadTextAsync();
                            items = content.Split('\n');
                            if (items.Length < 5) throw new InvalidDataException("invalid data"); ;
                        }
                        var fulltext = string.Join("\n", items.Skip(4));
                        await FullTextServiceInstance.Entry(items[1], items[2], fulltext, items[3]);

                        var file = await Container.EnsureFileAsync(items[1], true);
                        if (file.LastModified != null)
                        {
                            file.FullTextEntryDate = DateTime.UtcNow;
                            await file.SetMetadataAsync();
                        }
                        //セキュリティ上テキスト出力は行わない
                        EntryLog(new BlobUser(items[3]), "FullTextUpdate", items[1], "");
                    }
                    catch (Exception ex)
                    {
                        workex = ex;
                    }
                    if (blob != null) await blob.DeleteAsync();
                    if (workex != null) throw workex;
                }
                else if (items[0] == "copy")
                {
                    var src = FullTextServiceInstance.SearchById(items[1]);
                    if (src != null) await FullTextServiceInstance.Entry(items[2], src.ResultTitle, src.ResultContent, items[3]);

                    var file = await Container.EnsureFileAsync(items[2], true);
                    if (file.LastModified != null)
                    {
                        file.FullTextEntryDate = DateTime.UtcNow;
                        await file.SetMetadataAsync();
                    }
                }
                else if (items[0] == "rename")
                {
                    var src = FullTextServiceInstance.SearchById(items[1]);
                    if (src != null)
                    {
                        var title = BlobItem.GetFileName(items[2]);
                        await FullTextServiceInstance.Entry(items[2], title, src.ResultContent, items[3]);
                        await FullTextServiceInstance.DeleteEntry(items[1]);
                    }

                    var file = await Container.EnsureFileAsync(items[2], true);
                    if (file.LastModified != null)
                    {
                        file.FullTextEntryDate = DateTime.UtcNow;
                        await file.SetMetadataAsync();
                    }
                }
                queue.DeleteMessage(retrievedMessage);

            }
            catch(InvalidDataException ex)
            {
                queue.DeleteMessage(retrievedMessage);
                EntryLog(new BlobUser("SYSTEM"), "ExecuteUpdateFullText", "", ex.ToString());
            }
            return true;

        }


        public virtual async Task ExecuteUpdateFullText(int commitSize = 100, Action commitCallback = null)
        {
            int idx = 0;
            while (await ExecuteUpdateFullTextSub())
            {
                if (idx++ > commitSize)
                {
                    idx = 0;
                    Debug.WriteLine("fulltext Commit");
                    FullTextServiceInstance.Commit();
                    if (commitCallback != null) commitCallback();
                }
            }
            if (idx>0)
            {
                Debug.WriteLine("fulltext Commit");
                FullTextServiceInstance.Commit();
                if (commitCallback != null) commitCallback();
            }
        }


        public virtual void OptimizeFullText()
        {
            FullTextServiceInstance.Optimize();
        }

        public virtual void RenewSearcherIndex()
        {
            Debug.WriteLine("fulltext RenewSearcherIndex");
            FullTextServiceInstance.RenewSearcherIndex();
        }


        /// <summary>
        /// OData用のファイル作成処理
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="owner"></param>
        /// <param name="path"></param>
        /// <param name="title"></param>
        /// <param name="summary"></param>
        /// <returns></returns>
        public virtual async Task<BlobFile> CreateFileAsync(BlobUser user, string owner, string path, string title)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanWriteAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            if (await ExistsAsync(path)) throw new BlobException(BlobExceptionCode.AlreadyExists);

            var file = await Container.EnsureFileAsync(path, true);
            file.Owner = owner;
            file.Title = title;
            file.UploadDate = DateTimeOffset.UtcNow;
            using (var mem = new  MemoryStream())
            {
                await file.UploadAsync(mem);
            }
            await file.SetMetadataAsync();

            EntryLog(user, LogCreate, file.Path, null);
            return file;
        }


        /// <summary>
        /// OData用のファイル更新処理
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="owner"></param>
        /// <param name="path"></param>
        /// <param name="title"></param>
        /// <param name="summary"></param>
        /// <returns></returns>
        public virtual async Task<BlobFile> UpdateFileAsync(BlobUser user, string owner, string path, string title)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanWriteAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            if (!await ExistsAsync(path)) throw new BlobException(BlobExceptionCode.NotFoundFile);

            var file = await Container.EnsureFileAsync(path, true);
            if (file.LastModified != null)
            {
                canAccess = await CanModifyAsync(user, path);
                if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);
            }

            file.Owner = owner;
            file.Title = title;
            file.UploadDate = DateTimeOffset.UtcNow;
            await file.SetMetadataAsync();

            EntryLog(user, LogUpdate, file.Path, null);
            return file;
        }

        /// <summary>
        /// OData用のファイル取得
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<BlobFile> GetFileAsync(BlobUser user, string path)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanReadAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            if (!await ExistsAsync(path)) throw new BlobException(BlobExceptionCode.NotFoundFile);

            var file = await Container.EnsureFileAsync(path, true);

            EntryLog(user, LogGet, file.Path, null);
            return file;
        }

        /// <summary>
        /// OData用のファイル取得
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual async Task<List<BlobFile>> GetVesrionsAsync(BlobUser user, string path)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanReadAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            if (!await ExistsAsync(path)) throw new BlobException(BlobExceptionCode.NotFoundFile);

            var files = await Container.GetHistoriesAsync(path);

            EntryLog(user, LogGet, path, "version");
            return files;
        }

        /// <summary>
        /// アクセス権の設定
        /// </summary>
        /// <param name="user"></param>
        /// <param name="path"></param>
        /// <param name="accessTokens"></param>
        /// <returns></returns>
        public virtual async Task<BlobItem> SetAccessControl(BlobUser user, string path, string accessTokens)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canCtrl = await CanControlAsync(user, path);
            if (!canCtrl) throw new BlobException(BlobExceptionCode.NotAuthorization);

            if (!await ExistsAsync(path)) throw new BlobException(BlobExceptionCode.NotFoundFile);

            var item = await GetBlobItemAsync(user, path);
            item.AccessTokens.Clear();

            foreach (var token in accessTokens.Split(','))
            {
                var addToken = new BlobAccessToken(token);
                if (item.AccessTokens.Any(x => string.Equals(x.Role, addToken.Role, StringComparison.InvariantCultureIgnoreCase))) throw new BlobException(BlobExceptionCode.DuplicateRoleToken);
                item.AccessTokens.Add(addToken);
            }

            await item.SetMetadataAsync();
            await item.GetMetadataAsync(true);

            EntryLog(user, LogSet, path, accessTokens);

            return item;
        }

        /// <summary>
        /// パスワード設定
        /// </summary>
        /// <param name="user"></param>
        /// <param name="path"></param>
        /// <param name="passcode"></param>
        /// <returns></returns>
        public virtual async Task<BlobItem> SetFilePasscode(BlobUser user, string path, string passcode)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canCtrl = await CanControlAsync(user, path);
            if (!canCtrl) throw new BlobException(BlobExceptionCode.NotAuthorization);

            if (!await ExistsAsync(path)) throw new BlobException(BlobExceptionCode.NotFoundFile);

            var item = await GetBlobItemAsync(user, path);
            item.FilePasscode = passcode;

            await item.SetMetadataAsync();
            await item.GetMetadataAsync(true);

            EntryLog(user, LogSet, path, "passcode");

            return item;
        }

        /// <summary>
        /// パスワード設定
        /// </summary>
        /// <param name="user"></param>
        /// <param name="path"></param>
        /// <param name="passcode"></param>
        /// <returns></returns>
        public virtual async Task<BlobItem> SetProtectedMode(BlobUser user, string path, string mode)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canCtrl = await CanControlAsync(user, path);
            if (!canCtrl) throw new BlobException(BlobExceptionCode.NotAuthorization);

            if (!await ExistsAsync(path)) throw new BlobException(BlobExceptionCode.NotFoundFile);

            var item = await GetBlobItemAsync(user, path);
            item.ProtectedMode = mode;

            await item.SetMetadataAsync();
            await item.GetMetadataAsync(true);

            EntryLog(user, LogSet, path, "protected");

            return item;
        }
        /// <summary>
        /// ユーザロールの設定
        /// </summary>
        /// <param name="user"></param>
        /// <param name="path"></param>
        /// <param name="usersrole"></param>
        /// <returns></returns>
        public virtual async Task<BlobItem> SetUsersRole(BlobUser user, string path, string usersrole)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canCtrl = await CanControlAsync(user, path);
            if (!canCtrl) throw new BlobException(BlobExceptionCode.NotAuthorization);

            if (!await ExistsAsync(path)) throw new BlobException(BlobExceptionCode.NotFoundFile);

            var item = await GetBlobItemAsync(user, path);
            item.UsersRole = usersrole;

            await item.SetMetadataAsync();
            await item.GetMetadataAsync(true);

            EntryLog(user, LogSet, path, "usersRole:" + usersrole);

            return item;
        }

        public virtual async Task<BlobItem> SetBackupMode(BlobUser user, string path, bool? backup)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canCtrl = await CanControlAsync(user, path);
            if (!canCtrl) throw new BlobException(BlobExceptionCode.NotAuthorization);

            if (!await ExistsAsync(path)) throw new BlobException(BlobExceptionCode.NotFoundFile);

            var item = await GetBlobItemAsync(user, path);
            item.BackupMode = backup;

            await item.SetMetadataAsync();
            await item.GetMetadataAsync(true);

            EntryLog(user, LogSet, path, "backup:" + (backup.HasValue ? backup.ToString() : ""));

            return item;
        }

        public virtual UserInfo GetUserInfo(BlobUser user)
        {
            return UserInfo.Get(ConnectionString, CloudAccount.AccountName, user.Name);
        }

        public virtual void SaveUserInfo(UserInfo info)
        {
            info.Save(ConnectionString, CloudAccount.AccountName);
        }

        public virtual void DeleteUserInfo(BlobUser user)
        {
            UserInfo.Delete(ConnectionString, CloudAccount.AccountName, user.Name);

        }

        ///// <summary>
        ///// 一時的な認証チケットを作成します
        ///// </summary>
        //public string CreateAuthTicket(int expireMinutes, string path, string username)
        //{
        //    var ticket = new AuthTicket(expireMinutes, path, username);
        //    ticket.Save(ConnectionString);
        //    return ticket.RowKey;
        //}

        //public string RenewAuthTicket(AuthTicket ticket)
        //{
        //    ticket.Renew(60);
        //    ticket.Save(ConnectionString);
        //    return ticket.RowKey;
        //}

        ///// <summary>
        ///// 一時的な認証チケットを取得します
        ///// </summary>
        //public AuthTicket GetAuthTicket(string key)
        //{
        //    return AuthTicket.GetTicket(ConnectionString, key);
        //}

        bool disposed = false;


        public virtual async Task<List<BlobFile>> ListRecentAsync(BlobUser user, int targetDays)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);
            var retList = new List<BlobFile>();

            var today = DateTime.UtcNow.Date;
            var start = DateTime.UtcNow;
            var accesslog = new List<AccessLog>();
            var targetAction = new string[] { BlobService.LogUpload, BlobService.LogDownload };

            //for (int d = 0; d < targetDays; d++)
            //{
            //    var work = await GetLog(DateTime.UtcNow.Date.AddDays(-d), DateTime.UtcNow.Date.AddDays(-d), null, targetAction, null, 0);
            //    if (work != null) accesslog.AddRange(work);
            //}

            accesslog = await GetLog(DateTime.UtcNow.Date.AddDays(-targetDays), null, null, targetAction, null, 0);

            EntryLog(user, LogDebug, "", "ListRecentAsync:" + DateTime.UtcNow.Subtract(start).TotalSeconds.ToString());

            var recent = accesslog.Where(x => x.UserName == user.Name && targetAction.Contains(x.Action))
                .GroupBy(x => x.Path)
                .Select(x => new { Path = x.Key, Date = x.Max(y => y.Date) }).OrderByDescending(x => x.Date);


            foreach (var resultItem in recent)
            {
                if (resultItem.Path.EndsWith("/")) continue;
                try 
	            {
                    var file = await Container.EnsureFileAsync(resultItem.Path, true);
                    if (file.LastModified.HasValue)
                    {
                        file.Tag = resultItem.Date;
                        retList.Add(file);

                    }
	            }
	            catch (Exception ex)
	            {
                    Debug.WriteLine(ex);
	            }

            }

            return retList;
        }

        public virtual async Task<List<FileAccessLog>> ListFolderAccessLogAsync(BlobUser user, string path, int targetDays)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanControlAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);
            var accesslog = GetFileLog(path, 0);
            if (targetDays != 0)
            {
                accesslog = accesslog.Where(x => x.Date >= DateTime.UtcNow.Date.AddDays(-targetDays)).ToList();
            }

            EntryLog(user, LogDebug, path, "ListFolderAccessLogAsync" );

            return accesslog.OrderByDescending(x => x.Date).ToList();
        }

        public static void SetCurrentDomain(string domain)
        {
            if (System.Web.HttpContext.Current == null) return;
            HttpContext.Current.Items["__domain__"] = domain;
        }

        public static string GetCurrentDomain()
        {
            if (System.Web.HttpContext.Current == null) return "";

            if (HttpContext.Current.Items.Contains("__domain__")) return (string)HttpContext.Current.Items["__domain__"];
            if (SystemSettings.Instance.MultiDomain)
            {
                var items = HttpUtility.UrlDecode(System.Web.HttpContext.Current.Request.Url.PathAndQuery).Split('/');
                foreach (var item in items)
                {
                    if (item.StartsWith("(") && item.EndsWith(")"))
                    {
                        return item.Trim('(', ')').ToLower();
                    }
                }
            }
            return "";
        }


        public static BlobContainerConfig GetCurrentBlobContainerConfig()
        {
            var data = GetCurrentCloudAccount();
            if (data == null) throw new BlobException(BlobExceptionCode.NotAuthorization);
            if (string.IsNullOrEmpty(data.Config)) return null;
            return BlobContainerConfig.LoadFromString(data.Config);
        }

        public static CloudAccount GetCurrentCloudAccount()
        {
            return GetCloudAccount(null);
        }

        public static CloudAccount GetCloudAccount(string domain)
        {
            if (SystemSettings.Instance.MultiDomain)
            {
                domain = domain ?? GetCurrentDomain();
                if (string.IsNullOrEmpty(domain)) return null;
                return CloudAccount.Load(SystemSettings.Instance.BlobStorage, domain);
            }
            else
            {
                var account = CloudAccount.Load(SystemSettings.Instance.BlobStorage, "");
                return account;
            }
        }


        public virtual async Task StartCrawlAsync(BlobUser user,string path ="/")
        {
            var canControl = await CanControlAsync(user, path);
            var client = GetCrawlQueueClient();
            await client.AddMessageAsync(new CloudQueueMessage(path));
        }


        public virtual async Task<bool> CrawlAsync()
        {
            var client = GetCrawlQueueClient();
            var msg = await client.GetMessageAsync();
            if (msg == null) return false;
            client.DeleteMessage(msg);
            var path = msg.AsString;
            await CrawlAsync(client, path);
            return true;
        }

        private async Task CrawlAsync(CloudQueue client, string path)
        {
            var sw = Stopwatch.StartNew();
            EntryLog(new BlobUser("crawl"), LogCrawl, path, null);

            var isSearchedOn = await IsSerachedOnAsync(path);
            var isBackupOn = await IsBackupOnAsync(path);
            foreach (var item in await Container.GetFiles(path, 0, false))
            {
                var file = item as BlobFile;

                if (file != null)
                {
                    //Crawl対応のファイルでなければ除外
                    if (CloudAccount.GetCrawlableFileExtension().All(x => !file.Name.ToLower().EndsWith(x))) continue;

                    if (!isSearchedOn) continue;
                    try
                    {
                        EntryLog(new BlobUser("crawl"), "GetFullTextContent", file.Path, sw.Elapsed.ToString());
                        var text = await GetFullTextContent(file, isBackupOn);
                        EntryLog(new BlobUser("crawl"), "EnqueueInternal", file.Path, sw.Elapsed.ToString());
                        await EnqueueInternal(file.Path, file.Title, text, file.Owner);
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex);
                        EntryLog(new BlobUser("CrawlAsync"), "Error", file.Path, ex.ToString());
                    }
                }
                else
                {
                    if (item.Path != path)
                    {
                        await client.AddMessageAsync(new CloudQueueMessage(item.Path));
                    }
                }

            }

        }

        private async Task<string> GetFullTextContent(BlobFile file, bool isBackupOn)
        {
            var sw = Stopwatch.StartNew();
            string tempFile = null;
            string path = file.Path;
            try
            {
                tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.Name));
                using (var wr = new FileStream(tempFile, FileMode.CreateNew))
                {
                    if (!file.IsArchibveDirty)
                    {
                        await Container.DownloadArchiveAsync(file, wr);
                    }
                    else
                    {
                        if (isBackupOn)
                        {
                            await EnsureAchchiveAsync(file);
                            await Container.DownloadArchiveAsync(file, wr);
                        }
                        else
                        {
                            await Container.DownloadAsync(file, wr);
                        }
                    }
                }
                Debug.WriteLine("DownloadAsync " + file.Path + sw.Elapsed.ToString());
                var text = Extractor.Extract(tempFile);
                Debug.WriteLine("GetFullTextContent " + file.Path + sw.Elapsed.ToString());
                return text;
            }
            finally
            {
                try
                {
                    if (tempFile != null && File.Exists(tempFile)) System.IO.File.Delete(tempFile);
                }
                catch (Exception ex) { Debug.WriteLine(ex.ToString()); }
            }
        }

        public virtual async Task<List<BlobItem>> ListTempFilesAsync(BlobUser user, string prefix)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var path = GetParentPath(prefix);
            var canAccess = await CanReadAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            return await Container.ListTempFile(prefix);

        }

        public virtual async Task<BlobFile> SaveTempAsync(BlobUser user, string path, Stream stream)
        {
            var info = "";
            path = await CheckAndNormalizePathAsync(path);

            if (!await ExistsAsync(BlobFile.GetDirectoryPath(path))) throw new BlobException(BlobExceptionCode.InvalidDirectoryPath);

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanWriteAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            var file = await Container.EnsureFileAsync(path, true);
            await Container.SaveTempFile(file, stream);

            file.IsDeleted = true;
            await file.SetMetadataAsync();

            EntryLog(user, LogSaveTemp, path, info);
            return file;
        }

        public virtual async Task LoadTempAsync(BlobUser user, string path, Stream stream)
        {
            var sw = Stopwatch.StartNew();

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanReadAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            var file = await Container.EnsureFileAsync(path);
            await Container.LoadTempFile(file, stream);
            EntryLog(user, LogLoadTemp, file.Path, sw.Elapsed.ToString());

        }

        public virtual async Task DeleteTempAsync(BlobUser user, string path)
        {
            if (path.EndsWith("/")) throw new ArgumentException("path");

            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);

            var canAccess = await CanWriteAsync(user, path);
            if (!canAccess) throw new BlobException(BlobExceptionCode.NotAuthorization);

            var file = await Container.EnsureFileAsync(path, true);
            await Container.DeleteTempFile(file);
            EntryLog(user, LogDeleteTemp, file.Path, null);
        }

        /// <summary>
        /// 利用している容量を返す
        /// </summary>
        /// <returns></returns>
        public virtual async Task<long> GetUsedStorageSize()
        {
            return await Container.GetContainerSizeAsync();
        }

        public virtual string CreateBlobSas(BlobUser user, double expiryHour, bool canWrite)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);
            if (!user.IsAdmin) throw new BlobException(BlobExceptionCode.NotAuthorization);
            return Container.CreateBlobSas(expiryHour, canWrite);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        protected virtual void Dispose(bool disposing)
        {
            if (disposed) return;
            if (disposing)
            {
                if (_fullTextService != null)
                {
                    _fullTextService.Dispose();
                }
                if (_container != null)
                {
                    _container.Dispose();
                }
            }

            disposed = true;
        }

        public virtual bool Destory(BlobUser user)
        {
            if (user == null) throw new BlobException(BlobExceptionCode.NotFoundUser);
            if (!user.IsAdmin)throw new BlobException(BlobExceptionCode.NotAuthorization);
            var ret = Container.Destory();

            var storageAccount = CloudStorageAccount.Parse(this.ConnectionString);
            
            var tableClient = storageAccount.CreateCloudTableClient();
            var indexTable = tableClient.GetTableReference(CloudAccount.AccountName + "AspNetIndex");
            indexTable.DeleteIfExists();
            var rolesTable = tableClient.GetTableReference(CloudAccount.AccountName + "AspNetRoles");
            rolesTable.DeleteIfExists();
            var userTable = tableClient.GetTableReference(CloudAccount.AccountName + "AspNetUsers");
            userTable.DeleteIfExists();
            var fileTransferTable = tableClient.GetTableReference(CloudAccount.AccountName + "FileTransferWorkTask");
            fileTransferTable.DeleteIfExists();

            EntryLog(user, "Destory", null, null);
            return ret;
        }


        //public void SendDownloadMail(BlobUser user, IEnumerable<string> paths, FileTransferWorkTask task)
        //{

        //    var from = new System.Net.Mail.MailAddress(
        //                SystemSettings.Instance.FromMailAddress, "Cloud Storage Light");
        //    var to = new System.Net.Mail.MailAddress(task.From);
        //    var body = string.Join("\n", paths.Select(x => string.Format(Messages.MsgSendMailDownloadedFileBody, x, user.OperatorName)));
        //    MailServiceFactory.GetService().SendMailAsync(from, new MailAddress[] { to }, null, null, task.Title, body, false);


        //    //using (var smtpClient = new SmtpClient())
        //    //{
        //    //    var myMessage = new MailMessage();
        //    //    myMessage.From = new System.Net.Mail.MailAddress(
        //    //                        SystemSettings.Instance.FromMailAddress, "Cloud Storage Light");                //myMessage.From = new MailAddress(task.From);
        //    //    myMessage.To.Add(task.From);
        //    //    myMessage.Subject = task.Title;
        //    //    myMessage.Body = body;
        //    //    smtpClient.Send(myMessage);
        //    //}

        //}

        public virtual async Task SendFilesMail(BlobUser user, FileTransferWorkTask task)
        {
            var fileTransferTable = GetFileTransferWorkTaskTable();
 
            TableBatchOperation batchOperation = new TableBatchOperation();
            batchOperation.Insert(task);
            fileTransferTable.ExecuteBatch(batchOperation);


            var myMessage = CreateFileTransferMailMessage(task);
            await MailServiceFactory.GetService().SendMailAsync(myMessage);

        }

        private MailMessage CreateFileTransferMailMessage(FileTransferWorkTask task)
        {
            var myMessage = new MailMessage();
            myMessage.From = new System.Net.Mail.MailAddress(
                                SystemSettings.Instance.FromMailAddress, "Cloud Storage Light");                //myMessage.From = new MailAddress(task.From);
            foreach (var u in task.GetToUser())
            {
                if (task.Bcc)
                {
                    myMessage.Bcc.Add(u);
                }
                else
                {
                    myMessage.To.Add(u);
                }
            }
            myMessage.CC.Add(task.From);
            myMessage.Subject = task.Title;
//            var body = string.Format(Messages.MsgSendMailFileTransferBody, task.MailBody, task.LinkAddress);
            myMessage.Body = task.MailBody;
            return myMessage;
        }

        private MailMessage CreateFileTransferNotifyMailMessage(FileTransferWorkTask task)
        {
            var myMessage = new MailMessage();
            myMessage.From = new System.Net.Mail.MailAddress(
                                SystemSettings.Instance.FromMailAddress, "Cloud Storage Light");                //myMessage.From = new MailAddress(task.From);
            myMessage.To.Add(task.From);
            myMessage.Subject = "RE: "+task.Title;
            myMessage.Body =  string.Format(Messages.MsgSendMailNotifyFileTransferBody, task.History);
            return myMessage;
        }

        private MailMessage CreateFileTransferReminderMailMessage(FileTransferWorkTask task)
        {
            var reminderUsers = task.GetReminderToUser();
            if (reminderUsers.Count() == 0) return null;
            var myMessage = new MailMessage();
            myMessage.From = new System.Net.Mail.MailAddress(
                                SystemSettings.Instance.FromMailAddress, "Cloud Storage Light");                //myMessage.From = new MailAddress(task.From);
            foreach (var u in reminderUsers)
            {
                if (task.Bcc)
                {
                    myMessage.Bcc.Add(u);
                }
                else
                {
                    myMessage.To.Add(u);
                }
            }
            myMessage.CC.Add(task.From);
            myMessage.Subject = "RE: " + task.Title;
//            var body = string.Format(Messages.MsgSendMailFileTransferBody, task.MailBody, task.LinkAddress);
            myMessage.Body = string.Format(Messages.MsgSendMailReminderFileTransferBody, task.MailBody);
            return myMessage;
        }
        
        private CloudTable GetFileTransferWorkTaskTable()
        {
            var storageAccount = CloudStorageAccount.Parse(this.ConnectionString);
            var tableClient = storageAccount.CreateCloudTableClient();
            var fileTransferTable = tableClient.GetTableReference(CloudAccount.AccountName + "FileTransferWorkTask");
            fileTransferTable.CreateIfNotExists();
            return fileTransferTable;
        }

        public virtual FileTransferWorkTask GetFileTransferWorkTask(string worktaskId)
        {
            var fileTransferTable = GetFileTransferWorkTaskTable();

            var query = new TableQuery<FileTransferWorkTask>();
            query = query.Where(TableQuery.GenerateFilterCondition("PartitionKey",QueryComparisons.Equal,""));
            query = query.Where(TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, worktaskId));
            var list = fileTransferTable.ExecuteQuery(query);
            var result = list.FirstOrDefault();
            if (result == null) return null;
            if (result.Expired < DateTime.UtcNow) throw new BlobException(BlobExceptionCode.ExpiredTicket);
            return result;
        }

        public virtual void AddFileTransferHistory(string workTaskId, string history, string downloadUser)
        {
            var fileTransferTable = GetFileTransferWorkTaskTable();
            for (int i=0; i<10; i++)
            {
                try
                {
                    var task = GetFileTransferWorkTask(workTaskId);
                    TableBatchOperation batchOperation = new TableBatchOperation();
                    task.History = history + "\r\n" + task.History;
                    task.AddDownloadedUser(downloadUser);
                    task.HaveToSendMail = true;
                    batchOperation.Replace(task);
                    fileTransferTable.ExecuteBatch(batchOperation);
                    break;
                }
                catch (StorageException ex)
                {
                    if (ex.RequestInformation.HttpStatusCode == 412)
                        Debug.WriteLine("Optimistic concurrency violation – entity has changed since it was retrieved.");
                    else
                        throw;
                }
            }
        }

        private void UpdateFileTransferData(string workTaskId, bool? havToSendMail, int? reminderCount)
        {
            var fileTransferTable = GetFileTransferWorkTaskTable();
            for (int i = 0; i < 10; i++)
            {
                try
                {
                    var task = GetFileTransferWorkTask(workTaskId);
                    TableBatchOperation batchOperation = new TableBatchOperation();
                    if (havToSendMail.HasValue) task.HaveToSendMail = havToSendMail.Value;
                    if (reminderCount.HasValue) task.ReminderCount = reminderCount.Value;
                    batchOperation.Replace(task);
                    fileTransferTable.ExecuteBatch(batchOperation);
                    break;
                }
                catch (StorageException ex)
                {
                    if (ex.RequestInformation.HttpStatusCode == 412)
                        Debug.WriteLine("Optimistic concurrency violation – entity has changed since it was retrieved.");
                    else
                        throw;
                }
            }
        }

        public virtual async Task SendNotifyMail()
        {
            var currentCulture = Thread.CurrentThread.CurrentCulture;
            var currentUICulture = Thread.CurrentThread.CurrentUICulture;
            try
            {
                var fileTransferTable = GetFileTransferWorkTaskTable();
                var query = new TableQuery<FileTransferWorkTask>();
                query = query.Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, ""));
                var list = fileTransferTable.ExecuteQuery(query);
                foreach (var workTask in list)
                {
                    if (workTask.LCID != 0)
                    {
                        Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(workTask.LCID);
                        Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(workTask.LCID);
                    }

                    if (workTask.Expired.AddDays(3) < DateTime.UtcNow)
                    {
                        workTask.ETag = "*";
                        fileTransferTable.Execute(TableOperation.Delete(workTask));
                    }
                    if (workTask.HaveToSendMail)
                    {
                        var myMessage = CreateFileTransferNotifyMailMessage(workTask);
                        await MailServiceFactory.GetService().SendMailAsync(myMessage);
                        UpdateFileTransferData(workTask.Id, false, null);
                    }
                    if (workTask.Expired >= DateTime.UtcNow && workTask.GetNextReminderDate() <= DateTime.UtcNow)
                    {
                        var myMessage = CreateFileTransferReminderMailMessage(workTask);
                        if (myMessage != null)
                        {
                            await MailServiceFactory.GetService().SendMailAsync(myMessage);
                        }
                        UpdateFileTransferData(workTask.Id, null, workTask.ReminderCount + 1);
                    }
                }

            }
            finally
            {

                Thread.CurrentThread.CurrentCulture = currentCulture;
                Thread.CurrentThread.CurrentUICulture = currentUICulture;
            }
        }


        public virtual CustomAuthInfo GetCustomAuth(string path)
        {
            return Container.GetCustomAuth(path);
        }

        public virtual void SetCustomAuth(CustomAuthInfo authInfo)
        {
            Container.SetCustomAuth(authInfo);
        }

    }
}
